#include <stdio.h>
#include <iostream>
#include <fstream>
#include <log.hpp>
#include <memfs.h>

#include "sc.h"
#include "globals.h"

CLog clg;

int xml2spec(Mshell &trace, Mshell &ops, char **procs, char *file);
char **getproclist(Mshell &ops);

extern "C" int MAIN(int argc, char **argv)
{
  Mfs tfs, ofs;
  Mshell trace(tfs), ops(ofs);
  char *name;
  char **procs;

  if (argc < 3)
  {
    clg.err() << "Usage: " << argv[0] << " <trace> <benchmarked operations> [output]"
	      << std::endl;
    return -1;
  }
  else if (argc < 4)
  {
    name = "/dev/stdout";
  }
  else
  {
    name = argv[3];
  }

  std::ifstream tif(argv[1]);
  XMLParser txp(tif);
  trace.Import(txp, "/");
  
  std::ifstream oif(argv[2]);
  XMLParser oxp(oif);
  ops.Import(oxp, "/");
  
  procs = getproclist(ops);

  xml2spec(trace, ops, procs, name);
  
  return 0;
}

char *strippath(char *path)
{
  return strrchr(path, '/')+1;
}

char **getproclist(Mshell &ops)
{
  char **procs;
  char *element;
  int n=0;

  abortOnNULL(STREAM(clg.err.GetStream()), procs = (char**)malloc(sizeof(char*)));

  /* Find first operation */
  ops.FindInitDirectoryElements("/operations");
  if (!ops.FindGetNextElement(&element))
  {
    ops.ChDir(element);

    /* Find first occurrence */
    ops.FindInitDirectoryElements("instantiations");
    if (!ops.FindGetNextElement(&element))
    {
      ops.ChDir(element);

      /* Find benchmarked processors */
      ops.FindInitDirectoryElements(".");
      while (!ops.FindGetNextElement(&element))
      {
	if (!ops.ChDir(element))
	{
	  /* Add processor to list */
	  abortOnNULL(STREAM(clg.err.GetStream()),
		      procs = (char**)realloc(procs, (n+2)*sizeof(char*)));
	  abortOnNULL(STREAM(clg.err.GetStream()),
		      procs[n] = (char*)malloc((strlen(strippath(element))+1)*sizeof(char)));
	  strcpy(procs[n], strippath(element));
	  n++;
	}
      }

      procs[n] = NULL;
      return procs;
    }
  }

  abortErrorl((STREAM(clg.err.GetStream()), "No operations found in benchmark xml file"));

  return NULL;
}

int getprocid(char **procs, char *name)
{
  int i;

  for (i=0; procs[i]; i++)
  {
    if (!strcmp(procs[i], name)) return i;
  }

  return -1;
}

int xml2spec(Mshell &trace, Mshell &ops, char **procs, char *file)
{
  char *element, *element2, *element3, *name;
  int i, j, optype, line, argtype, intent, buffer, stream;
  char buf[PATH_MAX];
  unsigned long int bytes;
  long long int optime;
  time_t curtime = time(NULL);
  FILE *f;

  returnOnNULL(STREAM(clg.err.GetStream()), f = fopen(file, "w"));

  fprintf(f, "function [operations, network] = spec\n");
  fprintf(f, "%%SPEC Operation and network specification\n");
  fprintf(f, "%%   Generated on %s\n", asctime(localtime(&curtime)));

  for (i=0; procs[i]; i++)
    fprintf(f, "%% Processor %d is %s\n", i+1, procs[i]);
  fprintf(f, "\noperations = [];\n");

  /* Find traced operations */
  trace.FindInitDirectoryElements("/trace/operations");
  while (!trace.FindGetNextElement(&element))
  {
    Mshell op(trace, element);

    op.GetContent("type", &optype);
    op.GetContent("line", &line);
    op.GetContent("text", &name);

    fprintf(f, "%% %s op on line %d\n", name, line);

    /* Find operation arguments */
    op.FindInitDirectoryElements("arguments");
    while (!op.FindGetNextElement(&element2))
    {
      Mshell arg(op, element2);

      arg.GetContent("type", &argtype);

      /* Only use STREAM arguments for now */
      if (argtype == SC_VAR_STREAM)
      {
	arg.GetContent("i", &buffer);
	arg.GetContent("intent", &intent);
	
	sprintf(buf, "/trace/buffers/%d/stream", buffer);
	arg.GetContent(buf, &stream);
	sprintf(buf, "/trace/streams/%d/bytes", stream);
	arg.GetContent(buf, &bytes);
	
	sprintf(buf, "/operations/%d/instantiations/%d", optype, line);

	/* Find execution times for this operation */
	ops.FindInitDirectoryElements(buf);
	while (!ops.FindGetNextElement(&element3))
	{
	  if (!ops.ChDir(element3))
	  {
	    /* Add CPUtime/byte to table. Negative value if consuming data,
	     * positive if producing. */

	    ops.GetContent("time", &optime);
	    if (intent == SC_INTENT_IN) optime = -optime;
	    
	    fprintf(f, "operations(%d, %d, %d) = %e;\n",
		  atoi(strippath(element))+1, stream+1,
		    getprocid(procs, strippath(element3))+1, (double)optime/bytes/1000000.0);
	  }
	}
      }
    }
  }

  /* Assume 100MBit network */
  fprintf(f, "\n%% 100MBit full duplex switched ethernet\n");
  fprintf(f, "network = [];\n");

  for (i=0; procs[i]; i++)
  {
    for (j=0; procs[j]; j++)
    {
      if (i==j) fprintf(f, "network(%d, %d) = Inf;\n", i+1, j+1);
      else      fprintf(f, "network(%d, %d) = 12500000;\n", i+1, j+1);
    }
  }

  fclose(f);

  return 0;
}
